﻿using IdentityModel;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Text;

namespace D.Wx.JwtUtil
{
    /// <summary>
    /// 封装 JWT 自定义策略授权
    /// </summary>
    public static class JwtUtil
    {
        /// <summary>
        /// 添加Jwt授权中间件
        /// </summary>
        /// <param name="services">服务</param>
        /// <param name="policyName">策略名称</param>
        /// <param name="requireName">允许角色名称</param>
        /// <param name="securityKey">签名秘钥</param>
        /// <param name="issuer">谁颁发的</param>
        /// <param name="audience">颁发给谁</param>
        public static void AddJwtAuthorization(this IServiceCollection services, string policyName, string requireName, string securityKey, string issuer, string audience)
        {
            services.AddScoped<AppJwtBearerEvents>();
            services.AddSingleton<ITokenService, JwtService>();
            services.AddAuthorization(o =>
            {//授权
                //o.AddPolicy(policyName, policy => policy.AddRequirements(requireName));
                o.AddPolicy(policyName, policy => policy.RequireRole(requireName).Build());

                //o.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
                //o.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
                ////这个写法是错误的，这个是并列的关系，不是或的关系
                ////options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin,Client").Build());

                ////这个才是或的关系
                //o.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));

            })
            .AddAuthentication(o =>
            {
                o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(o =>
            {
                o.RequireHttpsMetadata = false;
                o.SaveToken = true;

                var keyByteArray = Encoding.ASCII.GetBytes(securityKey);
                var signingKey = new SymmetricSecurityKey(keyByteArray);
                var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

                // 设置token验证
                o.TokenValidationParameters = new TokenValidationParameters
                {//令牌验证参数

                    // 有效的签名算法列表，仅列出的算法是有效的（强烈建议不要设置该属性）
                    // 默认 null，即所有算法均可
                    ValidAlgorithms = new[] { SecurityAlgorithms.HmacSha256, SecurityAlgorithms.RsaSha256, SecurityAlgorithms.Aes128CbcHmacSha256 },
                    // 有效的token类型
                    // 默认 null，即所有类型均可
                    ValidTypes = new[] { JwtConstants.HeaderType },

                    // 有效的签发者，当需要验证签发者时，会将其与token中的签发者进行对比
                    ValidIssuer = issuer,
                    // 可以指定多个有效的签发者
                    //ValidIssuers = new [] { jwtOptions.Issuer },
                    // 是否验证签发者
                    // 默认 true
                    ValidateIssuer = true,

                    // 有效的受众，当需要验证受众时，会将其与token中的受众进行对比
                    ValidAudience = audience,
                    // 可以指定多个有效的受众
                    //ValidAudiences = new[] { jwtOptions.Audience };
                    // 是否验证受众
                    // 如果指定的值与token中的 aud 参数不匹配，则token将被拒绝
                    // 默认 true
                    ValidateAudience = true,

                    // 签发者用于token签名的密钥
                    // 对称加密，使用相同的key进行加签验签
                    IssuerSigningKey = signingKey,
                    // 非对称加密，使用私钥加签，使用公钥验签
                    //IssuerSigningKey = rsaSecurityPublicKey,

                    // 是否验证签发者用于token签名的密钥
                    // 默认 false
                    ValidateIssuerSigningKey = true,

                    // 是否验证token是否在有效期内（包括nbf和exp）使用当前时间与Token的Claims中的NotBefore和Expires对比
                    // 默认 true
                    ValidateLifetime = true,

                    // 是否要求token必须进行签名
                    // 默认 true
                    RequireSignedTokens = true,
                    // 是否要求token的Claims中必须包含Expires过期时间
                    // 默认 true
                    RequireExpirationTime = true,

                    // 设置 HttpContext.User.Identity.NameClaimType，便于 HttpContext.User.Identity.Name 取到正确的值
                    NameClaimType = JwtClaimTypes.Name,
                    // 设置 HttpContext.User.Identity.RoleClaimType，便于 HttpContext.User.Identity.IsInRole(xxx) 取到正确的值
                    RoleClaimType = JwtClaimTypes.Role,

                    // 时钟漂移
                    // 可以在验证token有效期时，允许一定的时间误差（如时间刚达到token中exp，但是允许未来5分钟内该token仍然有效）
                    // 默认 300s，即 5min
                    ClockSkew = TimeSpan.Zero,

                    // token解密密钥
                    //TokenDecryptionKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Total Bytes Length At Least 256!"))
                };

                // 当token验证通过后（执行完 JwtBearerEvents.TokenValidated 后），
                // 是否将token存储在 Microsoft.AspNetCore.Authentication.AuthenticationProperties 中
                // 默认 true
                o.SaveToken = true;

                // token验证处理器列表
                // 默认 有1个 JwtSecurityTokenHandler
                //o.SecurityTokenValidators.Clear();
                //o.SecurityTokenValidators.Add(new JwtSecurityTokenHandler());
                // 通过Post添加AppJwtSecurityTokenHandler

                // 受众，指该token是服务于哪个群体的（群体范围名），或该token所授予的有权限的资源是哪一块（资源的uri）
                // 该属性主要用于当其不为空，但 TokenValidationParameters.ValidAudience 为空时，将其赋值给 TokenValidationParameters.ValidAudience
                // 一般不使用该属性
                //o.Audience = jwtOptions.Audience;    

                o.EventsType = typeof(AppJwtBearerEvents);
            });
        }
    }
}
